package server

import (
	"context"
	"time"

	"github.com/bytedance/gopkg/util/logger"
	"github.com/juju/ratelimit"
	"google.golang.org/grpc"
	"google.golang.org/grpc/status"
	pcode "sniper/common/prpc/code"
)

type MethodName string

type RateLimitConfig struct {
	Cap             int64         `json:"cap"`
	Rate            float64       `json:"rate"`
	WaitMaxDuration time.Duration `json:"wait_max_duration"`
}

/*
gRPC的拦截器函数，用于实现服务端的限流功能。它接收一个configs参数，该参数是一个map，其中的键是方法名(MethodName)，值是对应方法的限流配置(RateLimitConfig)。

在函数内部，它首先创建了一个buckets的map，用于存储每个方法名对应的限流桶(ratelimit.Bucket)。然后，通过遍历configs参数，为每个方法名创建一个对应的限流桶，并将其存储在buckets中。

接下来，它返回一个匿名函数作为拦截器函数的实现。这个匿名函数接收ctx上下文、req请求对象、info方法信息和handler处理函数作为参数。在函数内部，它首先检查请求的方法名是否在buckets中存在对应的限流桶。
如果存在，它会尝试从限流桶中获取一个令牌，并设置最大等待时间。如果获取令牌失败，它会记录日志并返回一个限流错误。否则，它会调用handler处理函数来处理请求。

最后，如果请求的方法名不存在于buckets中，它会直接调用handler处理函数来处理请求。
*/

// RateLimitUnaryServerInterceptor ...
func RateLimitUnaryServerInterceptor(configs map[MethodName]RateLimitConfig) grpc.UnaryServerInterceptor {
	buckets := make(map[MethodName]*ratelimit.Bucket)
	for name, config := range configs {
		buckets[name] = ratelimit.NewBucketWithRate(config.Rate, config.Cap)
	}

	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
		if bucket, ok := buckets[MethodName(info.FullMethod)]; ok {
			if _, ok := bucket.TakeMaxDuration(1, configs[MethodName(info.FullMethod)].WaitMaxDuration); !ok {
				logger.CtxErrorf(ctx, "too many request")
				return nil, status.Errorf(pcode.CodeTooManyRequest, "too many request")
			}

			return handler(ctx, req)
		}

		return handler(ctx, req)
	}
}

/*
为了判断一个键是否存在于map中，可以使用以下语法：
value, ok := myMap[key]
其中，ok是一个布尔值，表示给定的键是否存在于myMap中。如果存在，value将是与键关联的值，ok将为true；如果不存在，value将是该值类型的零值，ok将为false。
*/

/*
注解：
buckets[MethodName(info.FullMethod)]

buckets是一个map，它的键是MethodName类型的值。MethodName是一个自定义的类型，它实际上是一个字符串类型的别名。

在这里，MethodName(info.FullMethod)是将info.FullMethod转换为MethodName类型的值，然后作为buckets的键来获取对应的限流桶。

如果将代码改为buckets[info.FullMethod]，那么info.FullMethod将直接作为字符串类型的值来获取对应的限流桶。这样做可能会导致类型不匹配的错误，因为buckets的键是MethodName类型，而不是字符串类型。

因此，为了确保类型匹配，需要使用MethodName(info.FullMethod)来作为buckets的键来获取对应的限流桶
*/
